/**
	@file
	Contains functions for working with partitions.
	Mount/unmount/read/write etc.
	Relies on diskIO.h
	Partitions are indexed by letters starting from 'a'.
	Drives and partitions are numerated from 0.
	Maximum number of partitions is defined in \ref MAX_PARTITION_NUMBER.

	@author Konstantin Kastanov
*/
#include "headers/partitioning.h"
#include "headers/diskIO.h"
#include "../api/headers/errors.h"
#include "headers/fat16.h"
#include "../memory/headers/kheap.h"

static Partition mountedPartitions[MAX_PARTITION_NUMBER]; //Mounted partitions are indexed with lowercase characters starting with 'a'

/**
	Makes necessary initializations of global variables.
	Should be called before calling any function in this file.
*/
void initPartitionning()
{
	u8int i;
	for(i = 0; i < MAX_PARTITION_NUMBER; i++)
	{
		mountedPartitions[i].size = 0; //Indicates that no partition mounted
	}
}

/**
	Returns a primary partition table information from a specified drive.
	Partitions are indexed from 0 to 3.
	Partition table is returned regardless of whether it exists or not.
	(Check partition size to determine if it is valid (invalid if 0)).
	@par Errors:	
				\li \ref ERR_PARTITION_NOT_FOUND if partitionNumber > 3
				\li	\ref ERR_DEVICE_NOT_FOUND if specified drive > (\ref MAX_DRIVE_NUMBER - 1) or does not exist

	@param[in] drive Drive where to look for a partition
	@param[in] partitionNumber Partition number from 0 to 3
	@param[out] res Pointer to a \ref Partition table struct where function puts results
*/
void getPartitionTable(u8int drive, u8int partitionNumber, Partition *res)
{
	u8int *partTable;
	u8int* buff;
	buff = (u8int*)kmalloc(SECTOR_SIZE);
	setLastError(NO_ERROR);
	if(partitionNumber > 3)
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return ;
	}

	diskRead(drive, buff, 0, 1);

	if(getLastError() != NO_ERROR)
		return;

	//------Partition table--//
	partTable = buff + FIRST_PARTITION_TABLE_ADDRESS + PARTITION_TABLE_SIZE * partitionNumber; //Calculate the address of required partition table

	res->bootIndicator = partTable[0];
	res->startingCHS = (u32int)((partTable[3] << 16) | (partTable[2] << 8) | partTable[1]);
	res->type = partTable[4];
	res->endingCHS = (u32int)((partTable[7] << 16) | (partTable[6] << 8) | partTable[5]);
	res->startingSector = (u32int)((partTable[11] << 24) | (partTable[10] << 16) | (partTable[9] << 8) | partTable[8]);
	res->size = (u32int)((partTable[15] << 24) | (partTable[14] << 16) | (partTable[13] << 8) | partTable[12]);
	res->drive = drive;
	res->number = partitionNumber;
	
	//-------Boot block-------//
	diskRead(drive, buff, res->startingSector, 1); //Read first sector of partition

	if(getLastError() != NO_ERROR)
		return;
	readBootBlock(buff, &(res->bootBlock));
	kfree(buff);
	return ;
}

/**
	Mounts specified partition from a specified drive. The name is automatically chosen.
	Only mounted partitions are accessible by read/write functions (except binary /ref diskRead///ref diskWrite).
	@par Errors:
				\li \ref ERR_DEVICE_NOT_FOUND if drive > (\ref MAX_DRIVE_NUMBER - 1)
				\li \ref ERR_ALREADY_MOUNTED if the specified partition is already mounted
				\li \ref ERR_PARTITION_NOT_FOUND if partitionNumber > 3 or 
					specified partition does not exist on a specified drive
				\li	\ref ERR_DEVICE_NOT_FOUND if specified drive > (\ref MAX_DRIVE_NUMBER - 1) or does not exist
	@param[in]	drive Drive where to look for a partition
	@param[in]	partitionNumber Partition number from 0 to 3
	@return		Letter (name) of mounted partition. -1 if operation failed (sets lastError see \ref getLastError())
*/
s8int mount(u8int drive, u8int partitionNumber)
{
	u8int i;
	s32int name = -1;

	if(drive > (MAX_DRIVE_NUMBER-1))
	{
		setLastError(ERR_DEVICE_NOT_FOUND);
		return -1;
	}

	//Check if the partition is already mounted
	for(i = 0; i < MAX_PARTITION_NUMBER; i++)
	{
		if((mountedPartitions[i].size != 0) && (mountedPartitions[i].drive == drive)
			&& (mountedPartitions[i].number == partitionNumber))
		{
			setLastError(ERR_ALREADY_MOUNTED);
			return -1;
		}
	}
	

	//Find a free name
	for(i = 0; (i < MAX_PARTITION_NUMBER) && (name < 0); i++)
	{
		if(mountedPartitions[i].size == 0)
		{
			name = i;
		}
	}

	if(name < 0)
	{
		setLastError(ERR_TOO_MANY_PARTITIONS_MOUNTED);
		return -1;
	}

	//Check if partition exist
	getPartitionTable(drive, partitionNumber, &(mountedPartitions[name]));

	if(getLastError() != NO_ERROR)
		return -1;

	if(mountedPartitions[name].size == 0)
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return -1;
	}
	return name + 'a'; //Convert to a character (drives are index with letters starting from 'a')
}


/**
	Reads specified number of sectors starting from \c sectorNumber from a specified partition.
	@par Errors:
				\li \ref ERR_PARTITION_NOT_FOUND if \c partition is out of range or not mounted
				\li \ref ERR_BAD_ADDRESS if specified \c sectorNumber or \c sectorNumber +
					\c sectorCount does not exist on partition.
	@param partition Partition letter (in lowercase). 
	@param[out] buff Buffer where to place read data.
	@param sectorNumber Number of the first sector to read (starting at the beginning of the partition (0 sector))
	@param sectorCount Number of sectors to read

	@return Number of sectors actually read
*/
u32int partRead(s8int partition, u8int *buff, u32int sectorNumber, u32int sectorCount)
{
	u8int index;

	if((partition < 'a') || (partition > ('a' + MAX_PARTITION_NUMBER - 1)))
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return 0;
	}

	index = partition - 'a';

	if(mountedPartitions[index].size == 0)
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return 0;
	}

	if((u32int)(sectorNumber + sectorCount) >= mountedPartitions[index].size)
	{
		setLastError(ERR_BAD_ADDRESS);
		return 0;
	}

	return diskRead(mountedPartitions[index].drive,
					buff,
					sectorNumber + mountedPartitions[index].startingSector,
					sectorCount);
}

/**
	Writes data from buffer to the specified sectors on the specified partition.
	@par Errors:
				\li \ref ERR_PARTITION_NOT_FOUND if \c partition is out of range or not mounted
				\li \ref ERR_BAD_ADDRESS if specified \c sectorNumber or \c sectorNumber +
					\c sectorCount does not exist on partition.
	@param partition Partition letter (in lowercase). 
	@param[out] buff Pointer to data to be written.
	@param sectorNumber Number of the first sector to read (starting at the beginning of the partition (0 sector))
	@param sectorCount Number of sectors to read

	@return Number of sectors actually written

	@warning Relies on diskWrite, which is not implemented yet
*/
u32int partWrite(s8int partition, u8int *buff, u32int sectorNumber, u32int sectorCount)
{
	u8int index;

	if((partition < 'a') || (partition > ('a' + MAX_PARTITION_NUMBER - 1)))
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return 0;
	}

	index = partition - 'a';

	if(mountedPartitions[index].size == 0)
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return 0;
	}

	if((u32int)(sectorNumber + sectorCount) >= mountedPartitions[index].size)
	{
		setLastError(ERR_BAD_ADDRESS);
		return 0;
	}

	return diskWrite(mountedPartitions[index].drive,
					buff,
					sectorNumber + mountedPartitions[index].startingSector,
					sectorCount);
}

Partition* getMountedPartitionInfo(s32int partition)
{
	partition -= 'a'; //Convert to a number

	if((partition < 0) || (partition > MAX_PARTITION_NUMBER))
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return 0;
	}

	if(mountedPartitions[partition].size == 0)  //Can't be placed in one (previous) if, because partition value
												//Should be checked first (array bounds)
	{
		setLastError(ERR_PARTITION_NOT_FOUND);
		return 0;
	}

	return &(mountedPartitions[partition]);
}